import { WorkflowFile } from '@openops/blocks-framework';
import { validateHost } from '@openops/server-shared';
import { isNil, isString } from '@openops/shared';
import axios from 'axios';
import isBase64 from 'is-base64';
import mime from 'mime-types';
import { apFileUtils } from '../../services/files.service';
import { ProcessorFn } from './types';

export const fileProcessor: ProcessorFn = async (_property, urlOrBase64) => {
  if (isNil(urlOrBase64) || !isString(urlOrBase64)) {
    return null;
  }
  try {
    const apFile = await apFileUtils.readWorkflowFile(urlOrBase64);
    if (!isNil(apFile)) {
      return apFile;
    }
    const file = handleBase64File(urlOrBase64);
    if (!isNil(file)) {
      return file;
    }
    return await handleUrlFile(urlOrBase64);
  } catch (e) {
    console.error(e);
    return null;
  }
};

function handleBase64File(propertyValue: string): WorkflowFile | null {
  if (!isBase64(propertyValue, { allowMime: true })) {
    return null;
  }
  const matches = propertyValue.match(/^data:([A-Za-z-+/]+);base64,(.+)$/); // example match: 
  if (!matches || matches?.length !== 3) {
    return null;
  }
  const base64 = matches[2];
  const extension = mime.extension(matches[1]) || 'bin';
  return new WorkflowFile(
    `unknown.${extension}`,
    Buffer.from(base64, 'base64'),
    extension,
  );
}

async function handleUrlFile(path: string): Promise<WorkflowFile | null> {
  await validateHost(path);

  const fileResponse = await axios.get(path, {
    responseType: 'arraybuffer',
  });

  const filename =
    getFileName(
      path,
      fileResponse.headers['content-disposition'],
      fileResponse.headers['content-type'],
    ) ?? 'unknown';
  const extension =
    filename.split('.').length > 1 ? filename.split('.').pop() : undefined;

  return new WorkflowFile(
    filename,
    Buffer.from(fileResponse.data, 'binary'),
    extension,
  );
}

function getFileName(
  path: string,
  disposition: string | null,
  mimeType: string | undefined,
): string | null {
  const url = new URL(path);
  if (isNil(disposition)) {
    const fileNameFromUrl =
      url.pathname.includes('/') && url.pathname.split('/').pop()?.includes('.')
        ? url.pathname.split('/').pop()
        : null;
    if (!isNil(fileNameFromUrl)) {
      return fileNameFromUrl;
    }
    const resolvedExtension = mimeType ? mime.extension(mimeType) : null;
    return `unknown.${resolvedExtension ?? 'bin'}`;
  }
  const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-.]+)(?:; ?|$)/i;
  if (utf8FilenameRegex.test(disposition)) {
    const result = utf8FilenameRegex.exec(disposition);
    if (result && result.length > 1) {
      return decodeURIComponent(result[1]);
    }
  }
  // prevent ReDos attacks by anchoring the ascii regex to string start and
  // slicing off everything before 'filename='
  const filenameStart = disposition.toLowerCase().indexOf('filename=');
  const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;

  if (filenameStart >= 0) {
    const partialDisposition = disposition.slice(filenameStart);
    const matches = asciiFilenameRegex.exec(partialDisposition);
    if (matches != null && matches[2]) {
      return matches[2];
    }
  }
  return null;
}
